<?
/**
 * Pork Formvalidator. validates fields by regexes and can sanatize them. Uses PHP filter_var built-in functions and extra regexes
 * @package pork
 */


/**
 * Pork.FormValidator
 * Validates arrays or properties by setting up simple arrays
 *
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2009
 * @version 1.0
 * @access public
 * Modified by TuanNH
 * Date Modified: 2012/06/05
 */
class FormValidator
{
	public static $regexes = Array(
		'date' => array('regex' => "^[0-9]{4}[-/][0-9]{1,2}[-/][0-9]{1,2}\$", 'message' => '%s must..'),
		'amount' => array('regex' => "^[-]?[0-9]+\$", 'message' => '%s must..'),
		'number' => array('regex' => "^[-]?[0-9,]+\$", 'message' => '%s must..'),
		'alfanum' => array('regex' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$", 'message' => '%s must..'),
		'not_empty' => array('regex' => "[a-z0-9A-Z]+", 'message' => '%s must..'),
		'words' => array('regex' => "^[A-Za-z]+[A-Za-z \\s]*\$", 'message' => '%s must..'),
		'phone' => array('regex' => "1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{3,4})(\se?x?t?(\d*))?\$", 'message' => '%s must..'),
		'zipcode' => array('regex' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$", 'message' => '%s must..'),
		'plate' => array('regex' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$", 'message' => '%s must..'),
		'price' => array('regex' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$", 'message' => '%s must..'),
		'2digitopt' => array('regex' => "^\d+(\,\d{2})?\$", 'message' => '%s must..'),
		'2digitforce' => array('regex' => "^\d+\,\d\d\$", 'message' => '%s must..'),
		'anything' => array('regex' => "^[\d\D]{1,}\$", 'message' => '%s must..'),
	);
	private $validations, $sanatations, $mandatories, $errors, $corrects, $fields, $error_messages;


	public function __construct($validations = array(), $mandatories = array(), $sanatations = array())
	{
		$this->validations = $validations;
		$this->sanatations = $sanatations;
		$this->mandatories = $mandatories;
		$this->errors = array();
		$this->corrects = array();
		$this->error_messages = array();
	}

	/**
	 * Validates an array of items (if needed) and returns true or false
	 *
	 */
	public function validate($items)
	{
		$this->fields = $items;
		$havefailures = false;

		// Extract validation types from Validations_array
		$validation_types = array();
		foreach ($this->validations as $key => $val) {
			$validation_types[$key] = $val['type'];
		}

		foreach ($items as $key => $val) {
			if (array_search($key, $validation_types) === false) {
				if (array_search($key, $this->mandatories) === false || (array_search($key, $this->mandatories) !== false && strlen($val) > 0)) {
					$this->corrects[] = $key;
					continue;
				} else {
					$havefailures = true;
					$this->addError($key, 'anything');
					$this->error_messages[] = "Invalid field $key";
				}
			} else {
				$result = self::validateItem($val, $validation_types[$key]);
				if ($result === false) {
					$havefailures = true;
					$this->addError($key, $validation_types[$key]);

					$this->error_messages[] = (!empty($this->validations[$key]['message']))
						? $this->validations[$key]['message']
						: sprintf(self::$regexes[$validation_types[$key]]['message'], ucfirst($key));
				} else {
					$this->corrects[] = $key;
				}
			}
		}

		return (!$havefailures);
	}

	/**
	 *
	 *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
	 */
	public function getScript()
	{
		$output = '';

		if (!empty($this->errors)) {
			$errors = array();
			foreach ($this->errors as $key => $val) {
				$errors[] = "'INPUT[name={$key}]'";
			}

			$output = '$$(' . implode(',', $errors) . ').addClass("unvalidated");';
			$output .= "alert('there are errors in the form');"; // or your nice validation here
		}
		if (!empty($this->corrects)) {
			$corrects = array();
			foreach ($this->corrects as $key) {
				$corrects[] = "'INPUT[name={$key}]'";
			}
			$output .= '$$(' . implode(',', $corrects) . ').removeClass("unvalidated");';
		}
		$output = "<script type='text/javascript'>{$output} </script>";
		return ($output);
	}

	/**
	 * Returns a JSON encoded array containing the names of fields with errors and those without.
	 * @return string
	 */
	public function getJSON()
	{
		$errors = array();
		$correct = array();
		$message = array();

		if (!empty($this->errors)) {
			foreach ($this->errors as $key => $val) {
				$errors[$key] = $val;
			}
		}

		if (!empty($this->corrects)) {
			foreach ($this->corrects as $key => $val) {
				$correct[$key] = $val;
			}
		}

		if (!empty($this->error_messages)) {
			foreach ($this->error_messages as $key => $val) {
				$message[$key] = $val;
			}
		}

		$output = array('errors' => $errors, 'correct' => $correct, 'message' => $message);

		return json_encode($output);
	}

	/**
	 *
	 * Sanatizes an array of items according to the $this->sanatations
	 * sanatations will be standard of type string, but can also be specified.
	 * For ease of use, this syntax is accepted:
	 * $sanatations = array('fieldname', 'otherfieldname'=>'float');
	 */
	public function sanatize($items)
	{
		foreach ($items as $key => $val) {
			if (array_search($key, $this->sanatations) === false && !array_key_exists($key, $this->sanatations)) continue;
			$items[$key] = self::sanatizeItem($val, @$this->sanatations[$key]);
		}
		return ($items);
	}


	/**
	 *
	 * Adds an error to the errors array.
	 */
	private function addError($field, $type = 'string')
	{
		$this->errors[$field] = $type;
	}

	/**
	 *
	 * Sanatize a single var according to $type.
	 * Allows for static calling to allow simple sanatization
	 */
	public static function sanatizeItem($var, $type = 'string')
	{
		$flags = NULL;
		switch ($type) {
			case 'url':
				$filter = FILTER_SANITIZE_URL;
				break;
			case 'int':
				$filter = FILTER_SANITIZE_NUMBER_INT;
				break;
			case 'float':
				$filter = FILTER_SANITIZE_NUMBER_FLOAT;
				$flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
				break;
			case 'email':
				$var = substr($var, 0, 254);
				$filter = FILTER_SANITIZE_EMAIL;
				break;
			case 'string':
			default:
				$filter = FILTER_SANITIZE_STRING;
				$flags = FILTER_FLAG_NO_ENCODE_QUOTES;
				break;

		}
//		$output = strip_tags(filter_var($var, $filter, $flags));
		$output = filter_var($var, $filter, $flags);
		return ($output);
	}

	/**
	 *
	 * Validates a single var according to $type.
	 * Allows for static calling to allow simple validation.
	 *
	 */
	public static function validateItem($var, $type)
	{
		// Check date
		if ($type == 'date') {
			$unix_time = strtotime($var);
			$formatted_date = date('Y-m-d', $unix_time);

			return ($formatted_date === $var);
		}

		// Using PHP standard filter
		$filter = false;
		switch ($type) {
			case 'email':
				$var = substr($var, 0, 254);
				$filter = FILTER_VALIDATE_EMAIL;
				break;
			case 'int':
				$filter = FILTER_VALIDATE_INT;
				break;
			case 'boolean':
				$filter = FILTER_VALIDATE_BOOLEAN;
				break;
			case 'ip':
				$filter = FILTER_VALIDATE_IP;
				break;
			case 'url':
				$filter = FILTER_VALIDATE_URL;
				break;
		}
		if ($filter !== false) {
			return (filter_var($var, $filter) !== false);
		}

		// Using given regex
		if (array_key_exists($type, self::$regexes)) {
			$is_valid = filter_var($var, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '!' . self::$regexes[$type]['regex'] . '!i'))) !== false;

			return ($is_valid);
		}
	}

	/**
	 * @return array
	 */
	public function getError()
	{
		return $this->error_messages;
	}

}